package lang

import (
	"go_gvm_mini/instructions/base"
	"go_gvm_mini/native"
	"go_gvm_mini/rtda"
	"go_gvm_mini/rtda/heap"
	"runtime"
	"time"
)

var _sysProps = map[string]string{
	"java.version":         "1.8.0",
	"java.vendor":          "jvm.go",
	"java.vendor.url":      "https://github.com/zxh0/jvm.go",
	"java.home":            "todo",
	"java.class.version":   "52.0",
	"java.class.path":      "todo",
	"java.awt.graphicsenv": "sun.awt.CGraphicsEnvironment",
	"os.name":              runtime.GOOS,   // todo
	"os.arch":              runtime.GOARCH, // todo
	"os.version":           "",             // todo
	"file.separator":       "/",            // todo os.PathSeparator
	"path.separator":       ":",            // todo os.PathListSeparator
	"line.separator":       "\n",           // todo
	"user.name":            "",             // todo
	"user.home":            "",             // todo
	"user.dir":             ".",            // todo
	"user.country":         "CN",           // todo
	"file.encoding":        "UTF-8",
	"sun.stdout.encoding":  "UTF-8",
	"sun.stderr.encoding":  "UTF-8",
}

func init() {
	native.Registers("java/lang/System", []*native.NaMethod{
		{
			"arraycopy",
			"(Ljava/lang/Object;ILjava/lang/Object;II)V",
			arrayCopy,
		}, {
			"initProperties",
			"(Ljava/util/Properties;)Ljava/util/Properties;",
			initProperties,
		}, {
			"setIn0",
			"(Ljava/io/InputStream;)V",
			setIn0,
		}, {
			"setOut0",
			"(Ljava/io/PrintStream;)V",
			setOut0,
		}, {
			"setErr0",
			"(Ljava/io/PrintStream;)V",
			setErr0,
		}, {
			"currentTimeMillis",
			"()J",
			currentTimeMillis,
		},
	})
}

/*
public static native void arraycopy(Object src,  int  srcPos,

	Object dest, int destPos,
	int length);
*/
func arrayCopy(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	src := vars.GetRef(0)
	srcPos := vars.GetInt(1)
	dest := vars.GetRef(2)
	destPos := vars.GetInt(3)
	length := vars.GetInt(4)

	if src == nil || dest == nil {
		panic("java.lang.NullPointerException")
	}

	if !checkArrayCopy(src, dest) {
		panic("java.lang.ArrayStoreException")
	}

	if srcPos < 0 || destPos < 0 || length < 0 ||
		srcPos+length > src.ArrayLength() ||
		destPos+length > dest.ArrayLength() {
		panic("java.lang.IndexOutOfBoundsException")
	}

	heap.ArrayCopy(src, dest, srcPos, destPos, length)
}

func checkArrayCopy(src *heap.Object, dest *heap.Object) bool {
	srcClass := src.Class()
	destClass := dest.Class()
	if !srcClass.IsArray() || !destClass.IsArray() {
		return false
	}
	if srcClass.ComponentClass().IsPrimitive() ||
		destClass.ComponentClass().IsPrimitive() {
		return srcClass == destClass
	}
	return true
}

// private static native Properties initProperties(Properties props);
func initProperties(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	props := vars.GetRef(0)

	stack := jFrame.OperandStack()
	stack.PushRef(props)

	// public synchronized Object setProperty(String key, String value)
	setPropMethod := props.Class().GetInstanceMethod("setProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;")
	jThread := jFrame.JThread()
	loader := jFrame.Method().Class().GetClassLoader()
	for key, val := range _sysProps {
		jKey := heap.JString(&loader, key)
		jVal := heap.JString(&loader, val)
		ops := rtda.NewOperandStack(3)
		ops.PushRef(props)
		ops.PushRef(jKey)
		ops.PushRef(jVal)
		shimFrame := rtda.NewShimFrame(jThread, ops)
		jThread.PushJFrame(shimFrame)

		base.InvokeMethod(shimFrame, setPropMethod)
	}
}

// private static native void setIn0(InputStream in);
func setIn0(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	in := vars.GetRef(0)

	sysClass := jFrame.Method().Class()
	sysClass.SetRefVar("in", "Ljava/io/InputStream;", in)
}

// private static native void setOut0(PrintStream out);
func setOut0(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	out := vars.GetRef(0)

	sysClass := jFrame.Method().Class()
	sysClass.SetRefVar("out", "Ljava/io/PrintStream;", out)
}

// private static native void setErr0(PrintStream err);
func setErr0(jFrame *rtda.JFrame) {
	vars := jFrame.LocalVars()
	err := vars.GetRef(0)

	sysClass := jFrame.Method().Class()
	sysClass.SetRefVar("err", "Ljava/io/PrintStream;", err)
}

// public static native long currentTimeMillis();
func currentTimeMillis(jFrame *rtda.JFrame) {
	millis := time.Now().UnixNano() / int64(time.Millisecond)
	stack := jFrame.OperandStack()
	stack.PushLong(millis)
}
